Make the annotation colour picker a single source of truth - #87
Merged
Conversation
Both creation modals showed the chosen colour twice — a highlighted preset swatch and a hex field — and only the swatch-click callback wrote both. Every other path moved one and left the other stale: typing in the field, and either modal opening, which resets the field alone. The annotation modal pre-fills the clicked trace's colour, almost never one of the six presets, so the highlight was wrong on most openings rather than occasionally. The colour actually saved always came from the hex field. The highlight was the half that lied. - Make the hex field the only place the colour lives. Presets become plain shortcuts that write into it; `_build_swatch_styles` and the style Output on both pick callbacks are gone, so there is no second indicator left to desync. `update_modal_ui` and `open_group_modal` needed no change — they already set the field, and the preview now follows it. - Add a preview swatch mirroring what would be saved, square and inset in the field's border so it does not read as a seventh preset. - Validate through the field's own `pattern` attribute and a CSS `:invalid` rule rather than a callback. `:not(:focus)` keeps it quiet mid-typing and it clears the moment the value is valid, neither of which a blur-triggered callback would have given. - Normalise at submit in `normalize_hex_color`, so `annotations.json` can no longer receive a non-hex string: previously only an empty field fell back and anything else was written verbatim. A code pasted without its `#` is now accepted rather than discarded. - Collapse the two byte-identical layout blocks into one `_color_picker` builder, with the shared literals moved to `constants.py` and `styles.py`. Behaviour change: a colour code that is not a valid six-digit hex resolves to the default instead of being saved as-is. Recorded in the changelog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported from use: the annotation colour picker's "selected" swatch did not always match the colour applied, nor the hex code shown next to it.
The bug
Both creation modals showed the chosen colour twice — a highlighted preset swatch and a hex field — kept in sync only by the swatch-click callback, which wrote both. Three other paths moved one side and left the other stale:
update_modal_ui)open_group_modal)The colour actually saved was always read from the hex field (
Stateonannotation-color-input/group-color-input), never from swatch state — so the highlight was the half that lied.It was also worse than "occasionally wrong". The annotation modal pre-fills
suggested_color, which is the clicked trace's line colour and almost never one of the six presets, so the highlight pointed at the wrong swatch on most openings.The fix
Rather than re-syncing two indicators more carefully, remove the second one. The hex field is now the only place a colour lives.
_build_swatch_stylesand thestyleOutput on both pick callbacks are deleted, so no second indicator remains to desync.update_modal_uiandopen_group_modalneeded no change — they already set the field, and the preview follows it.patternattribute, styled by a CSS:invalidrule.:not(:focus)keeps it quiet mid-typing and clears it the moment the value is valid — neither of which a blur-triggered callback would have given.normalize_hex_color) closes a data-integrity gap: previously only an empty field fell back, so a non-empty invalid string was written toannotations.jsonverbatim. A code pasted without its leading#is now accepted instead of discarded._color_pickerbuilder; shared literals moved toconstants.pyandstyles.pyper the repo's constants rule.Net effect on the callback layer: the design this replaced would have needed 4 new callbacks (live preview + blur-gated border, x2 modals). This needs 2, and
core_api.pyloses 138 lines of duplication.Behaviour change
A colour code that is not a valid six-digit hex now resolves to the default instead of being saved as-is. Recorded in the changelog under
[Unreleased].Review notes
tests/exercised_build_swatch_styles, either pick callback, or the colour fallbacks — verified before editing.core_api.specbundles the wholeassetsdirectory, so the newcolor_picker.cssships with no spec change.:has(). If it ever fails, the degradation is silent and graceful — the border stays grey and the preview swatch still shows the mismatch.Worth eyeballing in the running app: open either modal, type
bananaand tab out (border red, preview red), then type1abc9cwithout the#(border clears, preview teal). Then open the annotation modal from a trace click — the field should pre-fill that trace's colour with the preview matching, the case that was previously always wrong.🤖 Generated with Claude Code